/**
 * 
 */
"use strict";

var utils = utils || {};

utils.Clock = function() {
	this.reset();
};
utils.Clock.prototype.ms = function() {
	return Date.now() - this.t00;
};
utils.Clock.prototype.secs = function() {
	var now = Date.now();
	var t = now - this.t0;
	this.t0 = now;
	return 1e-3 * t;
};
utils.Clock.prototype.reset = function() {
	this.t00 = this.t0 = Date.now();
	this.t_on = undefined;
};
utils.Clock.prototype.ton = function(pt) {
	this.t_on = this.t_on || (Date.now() + pt);
	if (Date.now() >= this.t_on) {
		this.t_on = undefined;
		return true;
	} else {
		return false;
	}
};
utils.Clock.prototype.et = function() {
	if (this.t_on !== undefined)
		return Date.now() - this.t_on;
	else
		return 0;
};

utils.Vec2 = function(x, y) {
	this.set(x, y);
};
utils.Vec2.prototype.set = function(x, y) {
	this.x = x || 0;
	this.y = y || 0;
	return this;
};
utils.Vec2.prototype.abs = function() {
	return new utils.Vec2(Math.abs(this.x), Math.abs(this.y));
};
utils.Vec2.prototype.add = function(other) {
	if (other instanceof utils.Vec2A)
		return new utils.Vec2A(this.x + other.x, this.y + other.y, other.a);
	else
		return new utils.Vec2(this.x + other.x, this.y + other.y);
};
utils.Vec2.prototype.addA = function(angle) {
	var a1 = typeof angle === 'number' ? angle : 0;
	var a2 = this.a || 0;
	return new utils.Vec2A(this.x, this.y, (a1 + a2) % 360);
};
utils.Vec2.prototype.sub = function(other) {
	if (other instanceof utils.Vec2A)
		return new utils.Vec2A(this.x - other.x, this.y - other.y, -other.a);
	else
		return new utils.Vec2(this.x - other.x, this.y - other.y);
};
utils.Vec2.prototype.mul = function(other) {
	if (other !== undefined) {
	if (typeof other === 'number')
		return new utils.Vec2(this.x * other, this.y * other);
	else if (other instanceof utils.MatrixR2)
		return other.mul(this);
	else
	return new utils.Vec2(this.x * other.x, this.y * other.y);
	}
	return new utils.Vec2(this.x, this.y);
};
utils.Vec2.prototype.div = function(other) {
	if (typeof other === 'number')
		return new utils.Vec2(this.x / other, this.y / other);
	return new utils.Vec2(this.x / other.x, this.y / other.y);
};
utils.Vec2.prototype.normalize = function() {
	var l = this.length();
	if (l.fuzzyIsNull())
		throw new Error('Cannot divide by zero');
	return this.div(l);
};
utils.Vec2.prototype.lengthSq = function() {
	return this.x * this.x + this.y * this.y;
};
utils.Vec2.prototype.length = function() {
	return Math.sqrt(this.lengthSq());
};
utils.Vec2.prototype.distanceTo = function(other) {
	return this.sub(other).length();
};
utils.Vec2.prototype.equals = function(other) {
	return this.x.fuzzyEquals(other.x) && this.y.fuzzyEquals(other.y);
};
utils.Vec2.prototype.round = function() {
	return new utils.Vec2(Math.round(this.x), Math.round(this.y));
};

utils.Vec2A = function(x, y, angle) {
	utils.Vec2.call(this, x, y);
	this.a = angle || 0;
};
utils.Vec2A.prototype = new utils.Vec2();
utils.Vec2A.prototype.add = function(other) {
	var a = other.a || 0;
	var xOffs, yOffs;
	if (typeof other === 'number') {
		xOffs = (Math.sin(Math.rad(this.a % 360)) * other).round();
		yOffs = (Math.cos(Math.rad(this.a % 360)) * -other).round();
		return new utils.Vec2A(this.x + xOffs, this.y + yOffs, this.a);
	} else
		return new utils.Vec2A(this.x + other.x, this.y + other.y, (this.a + a) % 360);
};
utils.Vec2A.prototype.sub = function(other) {
	var a = other.a || 0;
	var xOffs, yOffs;
	if (typeof other === 'number') {
		xOffs = Math.sin(Math.rad(this.a % 360)) * other;
		yOffs = Math.cos(Math.rad(this.a & 360)) * -other;
		return new utils.Vec2A(this.x - xOffs, this.y - yOffs, this.a);
	} else
		return new utils.Vec2A(this.x - other.x, this.y - other.y, (this.a - a) % 360);
};
utils.Vec2A.prototype.toVec2 = function() {
	return new utils.Vec2(this.x, this.y);
};

utils.MatrixR2 = function(angle) {
	this.angle = angle;
	var rad = Math.rad(angle);
	this[0] = Math.cos(rad);
	this[1] = -Math.sin(rad);
	this[2] = Math.sin(rad);
	this[3] = Math.cos(rad);
};
utils.MatrixR2.prototype = new Array(4);
utils.MatrixR2.prototype.mul = function(vec2){
	if (!vec2 || !(vec2 instanceof utils.Vec2)) return;
	var vecProd = Math.matrixVecMul([[this[0], this[1]],[this[2], this[3]]], [vec2.x, vec2.y], 2, 2);
	var result = new utils.Vec2(vecProd[0], vecProd[1]);
	return result;
};

utils.Triangle = function(A, B, C) {
	if (!(A instanceof utils.Vec2 && B instanceof utils.Vec2 && C instanceof utils.Vec2))
		return;
	this.A = A;
	this.B = B;
	this.C = C;
};
utils.Triangle.prototype.heightOnAB = function() {
	var dist = this.A.distanceTo(this.B);
	if (dist === 0)
		return 0;
	return this.square() / (0.5 * dist);
};
utils.Triangle.prototype.heightOnBC = function() {
	var dist = this.B.distanceTo(this.C);
	if (dist === 0)
		return 0;
	return this.square() / (0.5 * dist);
};
utils.Triangle.prototype.heightOnAC = function() {
	var dist = this.A.distanceTo(this.C);
	if (dist === 0)
		return 0;
	return this.square() / (0.5 * dist);
};
utils.Triangle.prototype.circum = function() {
	return this.A.distanceTo(this.B) + this.A.distanceTo(this.C)
		+ this.B.distanceTo(this.C);
};
utils.Triangle.prototype.square = function() {
	var poly = new utils.Polygon(this.A, this.B, this.C);
	return poly.square();
};
utils.Polygon = function() {
	Array.call(this);
	for ( var arg in arguments) {
		if (arguments[arg] instanceof utils.Vec2)
			this.push(arguments[arg]);
	}
};
utils.Polygon.prototype = new Array();
utils.Polygon.prototype.square = function() {
	var sum = [];
	this.foreach(function(i, value, first, last) {
		sum.push((value.y + this[(i + 1) % this.length].y)
			* (value.x - this[(i + 1) % this.length].x));

	});
	var result = 0;
	for (var i = 0; i < sum.length; ++i) {
		result += sum[i];
	}
	return Math.abs(0.5 * result);
};
utils.Polygon.prototype.translate = function(angle, vec2) {
	var result = new utils.Polygon();
	// get a rotation matrix R²
	var matrix = new utils.MatrixR2(-angle);
	this.foreach(function(i, value, first, last) {
		// build the product of vector and rotation matrix
		var vec2a = matrix.mul(value).addA(angle);
		// add the offset
		if (vec2 instanceof utils.Vec2)
			vec2a = vec2a.add(vec2);
		result.push(vec2a);
	});
	return result;
};
utils.Line = function(A, B) {
	this.A = A || new utils.Vec2(0, 0);
	this.B = B || new utils.Vec2(0, 0);
};
utils.Line.prototype = {
	A : undefined,
	B : undefined
};
utils.Line.prototype.length = function() {
	return this.A.distanceTo(this.B);
};
utils.Line.prototype.crossAt = function(other) {
	if (other && other instanceof utils.Line) {
		var A1 = this.A;
		var AB1 = this.B.sub(this.A);
		var A2 = other.A;
		var AB2 = other.B.sub(other.A);
		var bDiv = (AB1.x * AB2.y - AB2.x * AB1.y);
		// no cross
		if (bDiv === 0)
			return undefined;
		// calculate vector cross point
		var i = (-A1.x * AB1.y + A1.y * AB1.x - AB1.x * A2.y + AB1.y * A2.x) / bDiv;
		var crossPoint = AB2.mul(i).add(A2);
		if (this.inXrange(crossPoint.x) && this.inYrange(crossPoint.y) && other.inXrange(crossPoint.x) && other.inYrange(crossPoint.y)){
			return crossPoint;
		}
	}
	return undefined;
};
utils.Line.prototype.getM = function() {
	var x = this.B.x - this.A.x;
	var y = this.B.y - this.A.y;
	if (x === 0)
		if (this.B.x >= this.A.x)
			return Number.POSITIVE_INFINITY;
		else
			return Number.NEGATIVE_INFINITY;
	return y / x;
};
utils.Line.prototype.inXrange = function(x) {
	var xvals = [ this.A.x, this.B.x ];
	if (this.A.x > this.B.x)
		xvals = xvals.reverse();
	return (x >= xvals[0] && x <= xvals[1]);
};
utils.Line.prototype.inYrange = function(y) {
	var yvals = [ this.A.y, this.B.y ];
	if (this.A.y > this.B.y)
		yvals = yvals.reverse();
	return (y >= yvals[0] && y <= yvals[1]);
};

utils.AlternatingBuffer = (function(size) {
	this.size = size;
	Array.call(this, size);
});
utils.AlternatingBuffer.prototype = new Array();
utils.AlternatingBuffer.prototype.reset = function() {
	this.i = 0;
	for (var i = 0; i < this.size; ++i) {
		this[i] = undefined;
	}
};
utils.AlternatingBuffer.prototype.put = function(v) {
	this[this.i] = v;
	this.i = (this.i + 1) % this.size;
};
utils.AlternatingBuffer.prototype.get = function() {
	return this[this.i];
};
utils.AlternatingBuffer.prototype.peek = function() {
	return this[(this.i + 1) % this.size];
};

/*
 * Extend basic JavaScript objects.
 */

Number.prototype.round = function(digits) {
	var m = Math.pow(10, digits || 4);
	return Math.round(this.valueOf() * m) / m;
};
Number.prototype.fuzzyIsNull = function() {
	return Math.abs(this.valueOf()) <= 0.000001;
};
Number.prototype.fuzzyEquals = function(other) {
	return Math.abs(this.valueOf() - other) <= 0.000001 * Math.min(Math
		.abs(this.valueOf()), Math.abs(other));
};
Number.prototype.rangeEquals = function(other, range) {
	var r = Math.abs(range || 0.000001);
	return Math.abs(this.valueOf() - other) <= r;
};
Number.prototype.isFinite = function() {
	return isFinite(this);
};

Math.rad = function(angle) {
	// get radiant (pieces of 2PI)
	return angle * 2 * Math.PI / 360;
};
Math.matrixVecMul = function (matrix,vec,m,n) {
	var result = new Array(m);
    for (var i = 0; i< m; ++i){                         // loop over matrix rows
        for (var j = 0; j< n;++j){                     // loop over vector elements
            result[i] = (result[i] || 0) + matrix[i][j] * vec[j];    // create the sum
        }
    }
    return result;
};

Array.prototype.remove = function(val) {
	var idx = this.indexOf(val);
	if (idx >= 0)
		this.splice(idx, 1);
};
Array.prototype.foreach = function(fn) {
	/***************************************************************************
	 * foreach(fn) --> calls function "fn" for each element in the array. fn
	 * arguments are: function(i, value, isFirst, isLast)
	 * 
	 * i = index value = each value; isFirst = True: first array entry; isLast =
	 * True: last array entry.
	 * 
	 * return with a result that not equals undefined breaks the iteration.
	 */
	if (typeof fn !== 'function')
		return;
	var result;
	for (var i = 0; i < this.length; ++i) {
		result = fn.call(this, i, this[i], i === 0, i + 1 === this.length);
		if (result !== undefined)
			return result;
	}
	return undefined;
};
Array.prototype.last = function() {
	if (this.length > 0)
		return this[this.length - 1];
	return undefined;
};
